using System;
using System.Collections;
using System.Text;
using System.Net;
using System.IO;
using System.Web;

namespace Crawler
{
	/// <summary>
	/// Understands the cycle of fetching and storing from a url in the url stack
	/// </summary>
	public class Fetcher : IFetcher
	{
		private IUrlStack urlStack;
		static ICentralFactory factory;
		private IDownloadStore downloadStore;
		private int notificationCount;
		private bool fetching;
		public static IFetcher NULL_FETCHER = NullFetcher.NULL_FETCHER;
		private ICrawlerContext crawlerContext;

		public Fetcher(ICentralFactory factory, ICrawlerContext crawlerContext)
		{
			Fetcher.factory = factory;
			this.urlStack = factory.GetUrlStack();
			this.downloadStore = factory.GetDownloadStore();
			this.crawlerContext = crawlerContext;
			this.notificationCount = 0;
			this.fetching = false;
		}

		public void FetchAndAddToDownloadStore()
		{
			this.fetching = true;
			if (urlStack.HasNext())
			{
				Uri site = GetUriFromUrlStack();
				String path = site.AbsoluteUri;
				if (path.IndexOf(@"javascript:") < 0)
				{
					if (crawlerContext.UrlStackEmpty)
						this.fetching = false;
					try
					{
						downloadStore.Add(new Key(path), new ContentFilter(site).FilterContents(path, site));
					}
					catch (WebObjectNotFoundException e)
					{
						downloadStore.Add(new Key(path),ResourceReference.NULL_REFERENCE  ) ;
						this.fetching = false;
					}
				}
				else
				{
					downloadStore.Add(new Key(path),ResourceReference.NULL_REFERENCE  ) ;
				}
			}

			this.fetching = false;
		}

		public void NotifyFetcherAboutUrlAdditionToUrlStack(object sender, UrlAddedToUrlStackArgs args)
		{
			this.notificationCount++;
			while (this.notificationCount > 0 && this.fetching == false)
			{
				FetchAndAddToDownloadStore();
				this.notificationCount--;
			}
		}

		private Uri GetUriFromUrlStack()
		{
			String path = (urlStack.GetNext().Value).ToLower();
			Uri site = new Uri(path);
			return site;
		}

		private static string ExtractElementFromUrlBasedOnSeparator(Uri url, char token)
		{
			string path = url.AbsoluteUri;
			Stack accumulator = new Stack();
			for (int i = path.Length - 1; i >= 0; i--)
			{
				if (path[i] == token) break;
				accumulator.Push(path[i]);
			}
			string extension = "";
			foreach (char c in accumulator)
				extension += c;
			return extension;
		}

		private static string ExtractNameFromUrl(Uri url)
		{
			return Fetcher.ExtractElementFromUrlBasedOnSeparator(url, '/');
		}

		private static string ExtractExtensionFromUrl(Uri url)
		{
			return Fetcher.ExtractElementFromUrlBasedOnSeparator(url, '.');
		}


		/// <summary>
		/// understands the screening of various text based data
		/// </summary>
		class TextFilter : IFilterOfDumpableData
		{
			public string FilterData(Uri site)
			{
				WebRequest wReq;
				WebResponse wResp;
				try
				{
					wReq = WebRequest.Create(site);
					wResp = wReq.GetResponse();
				}
				catch (Exception e)
				{
					throw new WebObjectNotFoundException("");
				}
				StreamReader reader = new StreamReader(wResp.GetResponseStream(), Encoding.ASCII);
				String stringDump = reader.ReadToEnd();
				wResp.Close();
				return stringDump;
			}
		}

		/// <summary>
		/// Understands the screening of non text related data 
		/// </summary>
		class BinaryFilter : IFilterOfNonDumpableData
		{
			public string FilterData(Uri site)
			{
				WebRequest wReq;
				WebResponse wResp;
				try
				{
					wReq = WebRequest.Create(site);
					wResp = wReq.GetResponse();
				}
				catch (Exception e)
				{
					throw new WebObjectNotFoundException("");
				}
				Stream downloadedStream = wResp.GetResponseStream();
				string pathInCache = "";
				pathInCache += Fetcher.factory.GetCachePath();
				pathInCache += "\\";
				pathInCache += HttpUtility.UrlDecode(Fetcher.ExtractNameFromUrl(site));
				FileStream destinationStream = new FileStream(pathInCache, FileMode.Create);
				BinaryWriter writer = new BinaryWriter(destinationStream);
				BinaryReader reader = new BinaryReader(downloadedStream);
				bool t = true;
				while (t)
				{
					try
					{
						writer.Write(reader.ReadByte());
					}
					catch
					{
						break;
					}
				}
				writer.Close();
				reader.Close();
				destinationStream.Close();
				wResp.Close();
				return pathInCache;
			}

		}


		/// <summary>
		/// understands the association of filters with their corresponding file types
		/// </summary>
		class ContentFilter : IContentFilter
		{
			enum TextFileExtensions
			{
				txt,
				html,
				htm,
				js,
				css,
				vbs,
				asp,
				php
			}

			enum BinaryFileExtensions
			{
				exe,
				bmp,
				jpg,
				gif,
				png,
				zip,
				swf,
			}


			IFilterOfDumpableData textFilter = new TextFilter();
			ITypeFilter currentFilter;
			Hashtable filterContainer = new Hashtable();
			IFilterOfNonDumpableData binaryFilter = new BinaryFilter();

			String GetFileExtension(Uri url)
			{
				return Fetcher.ExtractExtensionFromUrl(url);
			}

			#region IContentFilter Members

			public IResourceReference FilterContents(String path, Uri site)
			{
				currentFilter = (ITypeFilter) filterContainer[GetFileExtension(site)];
				if(currentFilter == null)
					currentFilter = textFilter;
				if (currentFilter is IFilterOfNonDumpableData)
					return new NonDumpable(currentFilter.FilterData(site));
				return new Dumpable(path, currentFilter.FilterData(site));
			}

			#endregion

			public ContentFilter(Uri site)
			{
				RegisterTextFileExtensionAndTheirFilters();
				RegisterBinaryFileExtensionsAndTheirFilters();
			}

			private void RegisterTextFileExtensionAndTheirFilters()
			{
				foreach (String s in Enum.GetNames(typeof (TextFileExtensions)))
				{
					filterContainer.Add(s, textFilter);
				}
			}

			private void RegisterBinaryFileExtensionsAndTheirFilters()
			{
				foreach (String s in Enum.GetNames(typeof (BinaryFileExtensions)))
				{
					filterContainer.Add(s, binaryFilter);
				}
			}
		}

		private class NullFetcher : IFetcher
		{
			public static NullFetcher NULL_FETCHER = new NullFetcher();

			private NullFetcher()
			{
			}

			#region IFetcher Members

			public void FetchAndAddToDownloadStore()
			{
			}

			public void NotifyFetcherAboutUrlAdditionToUrlStack(object sender, UrlAddedToUrlStackArgs args)
			{
			}

			#endregion
		}

	}
}